//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/os/cappmenu.h>
#include <buola/os/capplication.h>

#include <buola/xml.h>
#include <buola/io/cresourcefinder.h>
#include <buola/io/cfilefilter.h>
#include <buola/io/cfolder.h>
#include <buola/utility/cdesktopfile.h>
#include <buola/algorithm/comparison.h>
#include <buola/io/filesystem.h>

namespace buola { namespace os {

CAppMenu::SRule::~SRule()
{
}

bool CAppMenu::SFilenameRule::Check(CApplication *pApp)
{
    return (pApp->GetURI().Name()==mFilename);
}

bool CAppMenu::SCategoryRule::Check(CApplication *pApp)
{
    return pApp->HasCategory(mCategory);
}

CAppMenu::SBooleanRule::~SBooleanRule()
{
}

bool CAppMenu::SOrRule::Check(CApplication *pApp)
{
    for(int i=0;i<mSubRules.size();i++)
    {
        if(mSubRules[i]->Check(pApp)) return true;
    }
    return false;
}

bool CAppMenu::SAndRule::Check(CApplication *pApp)
{
    for(int i=0;i<mSubRules.size();i++)
    {
        if(!mSubRules[i]->Check(pApp)) return false;
    }
    return true;
}

bool CAppMenu::SNotRule::Check(CApplication *pApp)
{
    for(int i=0;i<mSubRules.size();i++)
    {
        if(mSubRules[i]->Check(pApp)) return false;
    }
    return true;
}

CAppMenu::CAppMenu(CAppMenu *pParent)
    :   mParent(pParent)
{
}

CAppMenu::CAppMenu(const std::string &pName)
    :   mParent(nullptr)
{
    mURL=io::config_finder().FindFile("menus/"+pName+".menu");
    xml::CDoc lDoc;
    lDoc.Load(mURL);

    xml::CNode lNode=lDoc.Root();

    if(!lNode.MatchesName("menu")) throw XData("malformed menu file");

    Parse(lNode);
    CalcCandidates();
    CalcTree(false);
    CalcTree(true);
    CalcDirectory();
}

CAppMenu::SRule *CAppMenu::ParseRule(xml::CNode pNode)
{
    if(pNode.MatchesName("filename"))
    {
        SFilenameRule* lRule=new SFilenameRule;
        lRule->mFilename=pNode.Content();
        lRule->mType="filename";
        return lRule;
    }
    else if(pNode.MatchesName("category"))
    {
        SCategoryRule* lRule=new SCategoryRule;
        lRule->mCategory=pNode.Content();
        lRule->mType="category";
        return lRule;
    }
    else //boolean
    {
        SBooleanRule *lRule=nullptr;

        if(pNode.MatchesName("include")||pNode.MatchesName("exclude")||
                    pNode.MatchesName("or"))
        {
            lRule=new SOrRule;
        }
        else if(pNode.MatchesName("and"))
        {
            lRule=new SAndRule;
        }
        else if(pNode.MatchesName("not"))
        {
            lRule=new SNotRule;
        }
        else
        {
            return nullptr;
        }

        for(auto lChild : pNode.Children())
        {
            SRule *lAdd=ParseRule(lChild);
            if(lAdd)
                lRule->mSubRules.push_back(lAdd);
        }

        lRule->mType=std::string(pNode.Name());
        return lRule;
    }
}

void CAppMenu::Parse(xml::CNode pNode)
{
    mOnlyUnallocated=false;
    for(auto lChild : pNode.Children())
    {
        if(lChild.MatchesName("menu"))
        {
            CAppMenu *lNewMenu=new CAppMenu(this);

            lNewMenu->Parse(lChild);
            mSubMenus.push_back(lNewMenu);
        }
        else if(lChild.MatchesName("appdir"))
        {
            mAppDirs.push_back(io::file(lChild.Content()));
        }
        else if(lChild.MatchesName("defaultappdirs"))
        {
            std::vector<io::CURI> lList=io::data_finder().FindFolders("applications");

            mAppDirs.insert(mAppDirs.end(),lList.rbegin(),lList.rend());
        }
        else if(lChild.MatchesName("directorydir"))
        {
            mDirectoryDirs.push_back(io::file(lChild.Content()));
        }
        else if(lChild.MatchesName("defaultdirectorydirs"))
        {
            std::vector<io::CURI> lList=io::data_finder().FindFolders("desktop-directories");

            mDirectoryDirs.insert(mDirectoryDirs.end(),lList.rbegin(),lList.rend());
        }
        else if(lChild.MatchesName("name"))
        {
            mName=utf32(lChild.Content());
        }
        else if(lChild.MatchesName("directory"))
        {
            mDirectory=lChild.Content();
        }
        else if(lChild.MatchesName("onlyunallocated"))
        {
            mOnlyUnallocated=true;
        }
        else if(lChild.MatchesName("notonlyunallocated"))
        {
            mOnlyUnallocated=false;
        }
        else if(lChild.MatchesName("deleted"))
        {
            mDeleted=true;
        }
        else if(lChild.MatchesName("notdeleted"))
        {
            mDeleted=false;
        }
        else if(lChild.MatchesName("include")||
                lChild.MatchesName("exclude"))
        {
            SRule *lRule=ParseRule(lChild);
            if(lRule)
                mRules.push_back(lRule);
        }
        else if(lChild.MatchesName("mergefile"))
        {
            //TODO
        }
        else if(lChild.MatchesName("mergedir"))
        {
            //TODO
        }
        else if(lChild.MatchesName("defaultmergedirs"))
        {
            //TODO
        }
        else if(lChild.MatchesName("legacydir"))
        {
            //TODO
        }
        else if(lChild.MatchesName("kdelegacydirs"))
        {
            //TODO
        }
        else if(lChild.MatchesName("move"))
        {
            //TODO
        }
    }
}

bool CAppMenu::empty()
{
    if(!mApps.empty()) return false;

    for(int i=0;i<mSubMenus.size();i++)
    {
        if(!mSubMenus[i]->empty()) return false;
    }

    return true;
}

void CAppMenu::CalcCandidates()
{
    for(int i=0;i<mAppDirs.size();i++)
    {
        CApplication::AddAllInFolder(mAppDirs[i],mCandidates);
    }

    for(int i=0;i<mSubMenus.size();i++)
        mSubMenus[i]->CalcCandidates();
}

void CAppMenu::Include(std::vector<CApplication*> &pDest,SRule *pRule,bool pUnalloc)
{
    for(auto i=mCandidates.begin();i!=mCandidates.end();++i)
    {
        if(!(*i)->ShouldDisplay()) continue;
        if(pUnalloc&&mUsedCandidates.count(*i)) continue;
        if(pRule->Check(*i))
        {
            mUsedCandidates.insert(*i);
            pDest.push_back(*i);
        }
    }

    if(mParent)
        mParent->Include(pDest,pRule,pUnalloc);
}

void CAppMenu::CalcTree(bool pPassTwo)
{
    if(!pPassTwo)
    {
        mUsedCandidates.clear();
    }

    if(pPassTwo==mOnlyUnallocated)
    {
        mApps.clear();
        for(int i=0;i<mRules.size();i++)
        {
            if(iequals(mRules[i]->mType,"include"))
            {
                Include(mApps,mRules[i],mOnlyUnallocated);
            }
            if(iequals(mRules[i]->mType,"exclude"))
            {

            }
        }
    }

    for(int i=0;i<mSubMenus.size();i++)
        mSubMenus[i]->CalcTree(pPassTwo);
}

io::CURI CAppMenu::FindDirectory(const std::string &pDirectory)
{
    for(int i=mDirectoryDirs.size()-1;i>=0;i--)
    {
        io::CURI lURI=mDirectoryDirs[i]/pDirectory;
        if(exists(lURI))
            return lURI;
    }

    if(mParent)
        return mParent->FindDirectory(pDirectory);
    return io::CURI();
}

void CAppMenu::CalcDirectory()
{
    for(int i=0;i<mSubMenus.size();i++)
        mSubMenus[i]->CalcDirectory();

    if(mDirectory.empty()) return;

    io::CURI lURI=FindDirectory(mDirectory);
    if(lURI.empty()) return;

    CDesktopFile lDesktopFile(lURI);

    std::wstring lTemp;

    lDesktopFile.Get("Desktop Entry","Type",lTemp);

    if(lTemp!=L"Directory")
        return;

    if(lDesktopFile.Get("Desktop Entry","Name",lTemp))
    {
        mName=lTemp;
    }

    lDesktopFile.Get("Desktop Entry","Comment",mComment);
    lDesktopFile.Get("Desktop Entry","Icon",mIcon);
}

CAppMenu *get_app_menu(const std::string &pName)
{
    static std::unordered_map<std::string,CAppMenu*> lAllMenus;
    
    auto i=lAllMenus.find(pName);
    if(i!=lAllMenus.end())
        return i->second;
    
    CAppMenu *lMenu=new CAppMenu(pName);
    
    lAllMenus[pName]=lMenu;
    return lMenu;
}

CAppMenu *get_default_app_menu()
{
    return get_app_menu("applications");
}

/*namespace os*/ } /*namespace buola*/ }
